#!/usr/bin/env python  # 指定脚本解释器为python
import sys, socket, time, threading  # 导入系统、套接字、时间、线程模块
import struct  # 导入用于处理二进制数据的结构模块
import selectors  # 导入用于I/O多路复用的选择器模块
import frp.ConnTool as ConnTool  # 导入ConnTool模块用于连接处理

sel = selectors.DefaultSelector()  # 创建默认的选择器实例

class Frps(threading.Thread):  # 定义Frps服务器类，继承自Thread
    def __init__(self, port, targetport,IP='0.0.0.0'):  # 初始化方法
        threading.Thread.__init__(self)  # 调用父类初始化方法
        print("Frps server listen at %s:%d" % (IP,port))
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建用户连接的TCP套接字
        self.sock.bind((IP, port))  # 绑定到指定端口
        # 设置为非阻塞模式
        self.sock.setblocking(False)  # 设置套接字为非阻塞模式
        self.sock.listen(100)  # 监听连接，最大连接数为100
        sel.register(self.sock,selectors.EVENT_READ,self.accept_connection)  # 注册到选择器监听读事件

        frpc_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建frpc连接的TCP套接字
        frpc_sock.bind((IP, targetport))  # 绑定到指定端口
        frpc_sock.setblocking(False)  # 设置套接字为非阻塞模式
        frpc_sock.listen(100)  # 监听连接，最大连接数为100
        sel.register(frpc_sock,selectors.EVENT_READ,self.accept_frp_connection)  # 注册到选择器监听读事件

        self.frpc_cmd_conn =None  # 初始化frpc命令连接为空
        self.userConns = []  # 初始化用户连接列表


    def heartbeat(self):  # 心跳方法
        while True:  # 循环发送心跳包
            if self.frpc_cmd_conn is not None:  # 如果frpc命令连接存在
                self.frpc_cmd_conn.send(struct.pack('i', 1))  # 发送心跳包(命令1)
            time.sleep(9)  # 每9秒发送一次心跳

    # 收到用户tcp 先不接收 向frpc发送指令 让其建立工作tcp
    def accept_connection(self,sock, mask):  # 接受用户连接方法
        userConn, addr = self.sock.accept()  # 接受用户连接
        userConn.setblocking(True)  # 设置为阻塞模式
        self.userConns.append(userConn)  # 将用户连接添加到列表
        print('收到用户请求')  # 打印收到用户请求信息
        if self.frpc_cmd_conn is None:  # 如果没有frpc命令连接
            # print(1)
            return  # 直接返回
            # time.sleep(0.5)
        print(2)  # 打印调试信息
        try:
            self.frpc_cmd_conn.send(struct.pack('i',2)) # 2建立新的tcp  # 发送命令2要求建立工作连接
        except IOError as err:  # 非阻塞模式下调用 阻塞操作recv 如果没有数据会抛出异常
            print(err)  # 打印异常信息
            pass  # 继续执行


    def accept_frp_connection(self,sock, mask):  # 接受frp连接方法
        frpc_conn, addr = sock.accept()  # 接受frpc连接
        frpc_conn.setblocking(False)  # 设置为非阻塞模式
        # 注册为可读套接字
        sel.register(frpc_conn, selectors.EVENT_READ, self.handle_controller_data)  # 注册到选择器监听读事件

    def handle_controller_data(self,frpc_conn, mask):  # 处理控制数据方法
        # print('frpc',frpc_conn,mask,self.userConns)
        try:
            data = frpc_conn.recv(4)  # Should be ready  # 接收4字节命令数据
            if data:  # 如果接收到数据
                cmd = struct.unpack('i',data)[0]  # 解包获取命令码
                print("cmd:",cmd)  # 打印命令码
                if cmd ==2:  # 是建立的工作tcp
                    sel.unregister(frpc_conn) # 不再监听  # 取消注册不再监听
                    userConn = self.userConns.pop() # 从队列中选一个用户线程来处理  # 从用户连接列表取出一个连接
                    frpc_conn.setblocking(True)  # 设置为阻塞模式
                    # print(userConn)
                    # print('cmd 2')
                    # userConn, addr  = self.sock.accept()
                    # self.frpc_conn.setblocking(True)
                    # print(userConn)
                    ConnTool.join(userConn, frpc_conn)  # 使用ConnTool建立双向数据传输
                elif cmd ==1 and self.frpc_cmd_conn!=frpc_conn: # 说明是首次收到1
                    self.frpc_cmd_conn = frpc_conn  # 设置frpc命令连接
                    threading.Thread(target=self.heartbeat).start()  # 启动心跳线程
        except IOError as err:  # 非阻塞模式下调用 阻塞操作recv 如果没有数据会抛出异常
            pass  # 捕获异常但不做处理

    def run(self):  # 运行方法(线程入口)
        while True:  # 循环处理事件
            events = sel.select()  # 使用选择器等待事件
            for key, mask in events:  # 遍历事件
                callback = key.data  # 获取回调函数
                callback(key.fileobj, mask)  # 调用回调函数处理事件


if __name__ == '__main__':  # 主程序入口
    try:
        frpsport = int(sys.argv[1])  # 获取命令行参数:frps端口
        userport = int(sys.argv[2])  # 获取命令行参数:用户端口
        IP = str(sys.argv[3]) #后端服务的 ip
        if IP=='':
            IP='0.0.0.0'
    except (ValueError, IndexError):  # 捕获参数错误异常
        print('Usage: %s frpsport userport' % sys.argv[0])  # 打印使用说明
        sys.exit(1)  # 退出程序

    print('Starting...')  # 打印启动信息
    Frps(userport, frpsport,IP).start()  # 创建Frps实例并启动线程
    print('frps server listen at 0.0.0.0:%d ,user port is %d' % (frpsport,userport))  # 打印服务器启动信息
    # Frps(8080, 7000).start()  # 被注释的测试代码

